package org.kernelab.basis;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.ListIterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Map.Entry;

/**
 * A light weighted class to parse JSON.
 * 
 * @author Dilly King
 * @version 1.3.7
 * @update 2011-05-02
 */
public class JSON extends LinkedHashMap<String, Object>
{

	/**
	 * A class to describe Array object using JSON format.
	 * 
	 * @author Dilly King
	 * 
	 */
	public static class JSAN extends JSON implements Iterable<Object>
	{
		private class JSANListIterator implements ListIterator<Object>
		{
			private int	cursor;

			private int	last;

			public JSANListIterator()
			{
				cursor = 0;
				last = -1;
			}

			@Override
			public void add(Object object)
			{
				JSAN.this.add(last, object);
			}

			@Override
			public boolean hasNext()
			{
				return cursor < JSAN.this.size();
			}

			@Override
			public boolean hasPrevious()
			{
				return cursor > 0;
			}

			@Override
			public Object next()
			{
				last = cursor++;
				return JSAN.this.get(String.valueOf(last));
			}

			@Override
			public int nextIndex()
			{
				return cursor;
			}

			@Override
			public Object previous()
			{
				last = --cursor;
				return JSAN.this.get(String.valueOf(last));
			}

			@Override
			public int previousIndex()
			{
				return cursor - 1;
			}

			@Override
			public void remove()
			{
				JSAN.this.remove(last);
			}

			@Override
			public void set(Object object)
			{
				JSAN.this.set(last, object);
			}
		}

		/**
		 * 
		 */
		private static final long	serialVersionUID	= -4343125403853941129L;

		public void add(int index, Object object)
		{
			if (index < 0) {
				index = 0;
			} else if (index > this.size()) {
				index = this.size();
			}

			for (int i = this.size() - 1; i >= index; i--) {
				this.put(String.valueOf(i + 1), this.get(i));
			}

			this.put(String.valueOf(index), object);
		}

		public void add(Object object)
		{
			this.put(String.valueOf(this.size()), object);
		}

		public void addAll(Iterable<Object> iterable)
		{
			for (Object obj : iterable) {
				this.add(obj);
			}
		}

		public void addAll(Object[] array)
		{
			for (Object obj : array) {
				this.add(obj);
			}
		}

		public Object get(int index)
		{
			if (!has(index)) {
				throw new NoSuchElementException();
			}

			return this.get(String.valueOf(index));
		}

		public boolean has(int index)
		{
			return index >= 0 && index < this.size();
		}

		@Override
		public Iterator<Object> iterator()
		{
			return new JSANListIterator();
		}

		public Object remove()
		{
			return this.remove(String.valueOf(this.size() - 1));
		}

		public Object remove(int index)
		{
			if (!has(index)) {
				throw new NoSuchElementException();
			}

			Object object = this.get(index);

			for (int i = index + 1; i < this.size(); i++) {
				this.put(String.valueOf(i - 1), this.get(String.valueOf(i)));
			}

			this.remove(String.valueOf(this.size() - 1));

			return object;
		}

		public Object set(int index, Object object)
		{
			if (!has(index)) {
				throw new NoSuchElementException();
			}

			Object old = this.get(index);
			this.put(String.valueOf(index), object);

			return old;
		}

	}

	/**
	 * 
	 */
	private static final long					serialVersionUID	= -4977780486670612620L;

	public static final char					OBJECT_BEGIN_CHAR	= '{';
	public static final String					OBJECT_BEGIN_MARK	= String.valueOf(OBJECT_BEGIN_CHAR);

	public static final char					OBJECT_END_CHAR		= '}';
	public static final String					OBJECT_END_MARK		= String.valueOf(OBJECT_END_CHAR);

	public static final char					ARRAY_BEGIN_CHAR	= '[';
	public static final String					ARRAY_BEGIN_MARK	= String.valueOf(ARRAY_BEGIN_CHAR);

	public static final char					ARRAY_END_CHAR		= ']';
	public static final String					ARRAY_END_MARK		= String.valueOf(ARRAY_END_CHAR);

	public static final char					PAIR_CHAR			= ',';
	public static final String					PAIR_MARK			= String.valueOf(PAIR_CHAR);

	public static final char					ATTR_CHAR			= ':';
	public static final String					ATTR_MARK			= String.valueOf(ATTR_CHAR);

	public static final char					QUOTE_CHAR			= '"';
	public static final String					QUOTE_MARK			= String.valueOf(QUOTE_CHAR);

	public static final char					ESCAPE_CHAR			= '\\';
	public static final String					ESCAPE_MARK			= String.valueOf(ESCAPE_CHAR);

	public static final int						PAIR_NAME			= 0;

	public static final int						PAIR_VALUE			= 1;

	public static final int						NOT_BEGIN			= -1;

	public static final char[]					KEY_WORDS			= { QUOTE_CHAR,
			ESCAPE_CHAR, '/',										};

	public static final Map<Character, String>	INVISIBLE_KEY_WORDS	= new HashMap<Character, String>();

	static {
		INVISIBLE_KEY_WORDS.put('\b', "\\b");
		INVISIBLE_KEY_WORDS.put('\f', "\\f");
		INVISIBLE_KEY_WORDS.put('\n', "\\n");
		INVISIBLE_KEY_WORDS.put('\r', "\\r");
		INVISIBLE_KEY_WORDS.put('\t', "\\t");
	}

	public static String EscapeString(String string)
	{
		StringBuilder sb = new StringBuilder(string);

		for (int i = 0; i < sb.length(); i++) {
			char c = sb.charAt(i);
			if (IsKeyword(c)) {
				sb.insert(i, ESCAPE_CHAR);
				i++;
			} else if (INVISIBLE_KEY_WORDS.containsKey(c)) {
				sb.deleteCharAt(i);
				sb.insert(i, INVISIBLE_KEY_WORDS.get(c));
				i++;
			}
		}

		return sb.toString();
	}

	public static boolean IsArray(JSON json)
	{
		return json instanceof JSAN;
	}

	public static final boolean IsKeyword(char word)
	{
		boolean is = false;

		for (char c : KEY_WORDS) {
			if (word == c) {
				is = true;
				break;
			}
		}

		return is;
	}

	/**
	 * @param args
	 */
	public static void main(String[] args)
	{
		String s = "{\"k\":true}";
		JSON j = JSON.Parse(s);
		Tools.debug(j.toString());
	}

	public static JSON Parse(CharSequence json)
	{
		json = new StringBuilder(json).toString().trim();

		JSON object = null;
		if (json.charAt(0) == ARRAY_BEGIN_CHAR) {
			object = new JSAN();
		} else {
			object = new JSON();
		}

		return Parse(json, object);
	}

	public static JSON Parse(CharSequence jsonString, JSON object)
	{
		StringBuilder json = new StringBuilder(jsonString);

		if (object == null) {
			return Parse(jsonString);
		} else {
			object.clear();
		}

		int arrayIndex = NOT_BEGIN;
		String name = null;
		Object value = null;
		int nail = NOT_BEGIN;
		boolean escape = false;

		i: for (int i = 0; i < json.length(); i++) {

			char c = json.charAt(i);

			if (c == ESCAPE_CHAR) {
				escape = !escape;
				if (escape) {
					json.deleteCharAt(i);
					i--;
					continue i;
				}
			}

			if (!escape) {

				switch (c)
				{
					case OBJECT_BEGIN_CHAR:
						if (i != 0) {
							int match = Tools.dualMatchIndex(json, OBJECT_BEGIN_CHAR,
									OBJECT_END_CHAR, i);
							value = Parse(json.substring(i, match + 1));
							i = match;
							nail = NOT_BEGIN;
						} else {
							nail = i + 1;
						}
						break;

					case OBJECT_END_CHAR:
						if (name != null) {
							if (nail != NOT_BEGIN) {
								value = Value(json.substring(nail, i).trim());
							}
							object.put(name, value);
						}
						break i;

					case ARRAY_BEGIN_CHAR:
						if (i != 0) {
							int match = Tools.dualMatchIndex(json, ARRAY_BEGIN_CHAR,
									ARRAY_END_CHAR, i);
							value = Parse(json.substring(i, match + 1));
							i = match;
							nail = NOT_BEGIN;
						} else {
							nail = i + 1;
							arrayIndex++;
						}
						break;

					case ARRAY_END_CHAR:
						if (nail != NOT_BEGIN && nail != i) {
							value = Value(json.substring(nail, i).trim());
						}
						if (value != null) {
							object.put(String.valueOf(arrayIndex), value);
						}
						break i;

					case PAIR_CHAR:
						if (nail != NOT_BEGIN) {
							value = Value(json.substring(nail, i).trim());
						}
						if (arrayIndex > NOT_BEGIN) {
							name = String.valueOf(arrayIndex);
							arrayIndex++;
						}
						object.put(name, value);
						nail = i + 1;
						name = null;
						value = null;
						break;

					case ATTR_CHAR:
						name = TrimQuotes(json.substring(nail, i));
						nail = i + 1;
						break;

					case QUOTE_CHAR:

						break;
				}
			}

			escape = false;
		}

		return object;
	}

	public static String Serialize(JSON json)
	{
		StringBuilder sb = new StringBuilder();

		boolean isArray = IsArray(json);
		boolean isFirst = true;
		for (Entry<String, Object> entry : json.entrySet()) {

			Object object = entry.getValue();
			if (object != null) {

				if (isFirst) {
					isFirst = false;
				} else {
					sb.append(PAIR_CHAR);
				}

				if (!isArray) {
					sb.append(QUOTE_CHAR);
					sb.append(EscapeString(entry.getKey()));
					sb.append(QUOTE_CHAR);
					sb.append(ATTR_CHAR);
				}

				String value = null;
				if (object instanceof JSON) {
					JSON j = (JSON) object;
					value = Serialize(j);
				} else if (object instanceof String) {
					value = QUOTE_CHAR + EscapeString(object.toString()) + QUOTE_CHAR;
				} else {
					value = object.toString();
				}

				sb.append(value);
			}
		}

		if (isArray) {
			sb.insert(0, ARRAY_BEGIN_CHAR);
			sb.append(ARRAY_END_CHAR);
		} else {
			sb.insert(0, OBJECT_BEGIN_CHAR);
			sb.append(OBJECT_END_CHAR);
		}

		return sb.toString();
	}

	public static final String TrimQuotes(String string)
	{
		return string.trim().replaceAll("^" + QUOTE_CHAR, "")
				.replaceAll(QUOTE_CHAR + "$", "");
	}

	public static Object Value(String string)
	{
		Object value = null;

		try {
			if (string.startsWith(QUOTE_MARK) && string.endsWith(QUOTE_MARK)) {
				value = TrimQuotes(string);
			} else if (Variable.isInteger(string)) {
				value = Integer.valueOf(string);
			} else if (Variable.isDouble(string)) {
				value = Double.valueOf(string);
			} else if (string.equals("true") || string.equals("false")) {
				value = Boolean.valueOf(string);
			} else {
				value = string;
			}
		} catch (NumberFormatException e) {
			value = string;
		}

		return value;
	}

	public JSON()
	{
		super();
	}

	@Override
	public String toString()
	{
		return Serialize(this);
	}
}
